//
// Created by 覃州 on 2024/4/8.
//

#ifndef CANON_CONFIG_H
#define CANON_CONFIG_H

#include <memory>
#include <string>
#include <boost/lexical_cast.hpp>
#include <utility>
#include <yaml-cpp/yaml.h>
#include <unordered_set>

#include "log.h"
#include "./util/utils.h"

namespace canon {

class ConfigVarBase {
public:
    using ptr = std::shared_ptr<ConfigVarBase>;

    explicit ConfigVarBase(std::string name, const std::string &description) : m_name(name), m_description(description)
    {
        std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
    }

    virtual ~ConfigVarBase() = default;

    [[nodiscard]] const std::string &getName() const { return m_name; }

    const std::string &getDescription() const { return m_description; }

    virtual std::string toString() = 0;
    virtual bool fromString(const std::string &val) = 0;

    virtual std::string getTypeName() = 0;

private:
    std::string m_name;
    std::string m_description;
};

template <class F, class T> // F to T
class LexicalCast {
public:
    T operator()(const F &v) { return boost::lexical_cast<T>(v); }
};

template <class T>
class LexicalCast<std::string, std::vector<T>> {
public:
    std::vector<T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::vector<T> vec;
        std::stringstream ss;
        for (auto &&i: node) {
            ss.str("");
            ss << i;
            vec.push_back(LexicalCast<std::string, T>()(ss.str()));
        }
        return vec;
    }
};

template <class T>
class LexicalCast<std::vector<T>, std::string> {
public:
    std::string operator()(const std::vector<T> &v)
    {
        YAML::Node node;
        for (auto &i: v) {
            node.push_back(YAML::Node(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

template <class T>
class LexicalCast<std::string, std::list<T>> {
public:
    std::list<T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::list<T> vec;
        std::stringstream ss;
        for (auto &&i: node) {
            ss.str("");
            ss << i;
            vec.push_back(LexicalCast<std::string, T>()(ss.str()));
        }
        return vec;
    }
};

template <class T>
class LexicalCast<std::list<T>, std::string> {
public:
    std::string operator()(const std::list<T> &v)
    {
        YAML::Node node;
        for (auto &i: v) {
            node.push_back(YAML::Node(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

template <class T>
class LexicalCast<std::string, std::set<T>> {
public:
    std::set<T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::set<T> vec;
        std::stringstream ss;
        for (auto &&i: node) {
            ss.str("");
            ss << i;
            vec.insert(LexicalCast<std::string, T>()(ss.str()));
        }
        return vec;
    }
};

template <class T>
class LexicalCast<std::set<T>, std::string> {
public:
    std::string operator()(const std::set<T> &v)
    {
        YAML::Node node;
        for (auto &i: v) {
            node.push_back(YAML::Node(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

template <class T>
class LexicalCast<std::string, std::unordered_set<T>> {
public:
    std::unordered_set<T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::unordered_set<T> vec;
        std::stringstream ss;
        for (auto &&i: node) {
            ss.str("");
            ss << i;
            vec.insert(LexicalCast<std::string, T>()(ss.str()));
        }
        return vec;
    }
};

template <class T>
class LexicalCast<std::unordered_set<T>, std::string> {
public:
    std::string operator()(const std::unordered_set<T> &v)
    {
        YAML::Node node;
        for (auto &i: v) {
            node.push_back(YAML::Node(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

/**
 * @brief 类型转换模板类片特化(YAML String 转换成 std::map<std::string, T>)
 */
template <class T>
class LexicalCast<std::string, std::map<std::string, T>> {
public:
    std::map<std::string, T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::map<std::string, T> vec;
        std::stringstream ss;
        auto map = node.as<std::map<std::string, YAML::Node>>();
        for (auto &it: map) {
            ss.str("");
            ss << it.second;
            vec.insert(std::make_pair(it.first, LexicalCast<std::string, T>()(ss.str())));
        }
        return vec;
    }
};

/**
 * @brief 类型转换模板类片特化(std::map<std::string, T> 转换成 YAML String)
 */
template <class T>
class LexicalCast<std::map<std::string, T>, std::string> {
public:
    std::string operator()(const std::map<std::string, T> &v)
    {
        YAML::Node node(YAML::NodeType::Map);
        for (auto &i: v) {
            node[i.first] = YAML::Load(LexicalCast<T, std::string>()(i.second));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

/**
 * @brief 类型转换模板类片特化(YAML String 转换成 std::unordered_map<std::string, T>)
 */
template <class T>
class LexicalCast<std::string, std::unordered_map<std::string, T>> {
public:
    std::unordered_map<std::string, T> operator()(const std::string &v)
    {
        YAML::Node node = YAML::Load(v);
        typename std::unordered_map<std::string, T> vec;
        std::stringstream ss;
        auto map = node.as<std::map<std::string, YAML::Node>>();
        for (auto &it: map) {
            ss.str("");
            ss << it.second;
            vec.insert(std::make_pair(it.first, LexicalCast<std::string, T>()(ss.str())));
        }
        return vec;
    }
};

/**
 * @brief 类型转换模板类片特化(std::unordered_map<std::string, T> 转换成 YAML String)
 */
template <class T>
class LexicalCast<std::unordered_map<std::string, T>, std::string> {
public:
    std::string operator()(const std::unordered_map<std::string, T> &v)
    {
        YAML::Node node(YAML::NodeType::Map);
        for (auto &i: v) {
            node[i.first] = YAML::Load(LexicalCast<T, std::string>()(i.second));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

template <class T, class FromStr = LexicalCast<std::string, T>, class ToStr = LexicalCast<T, std::string>>
class ConfigVar : public ConfigVarBase {
public:
    using ptr = std::shared_ptr<ConfigVar>;
    typedef std::function<void(const T& old_value, const T& new_value)> on_change_cb;

    ConfigVar(const std::string &name, const T &default_value, const std::string &description = "")
            : ConfigVarBase(name, description), m_val(default_value)
    {
    }

    std::string toString() override
    {
        try {
            return ToStr()(m_val);
        }
        catch (std::exception &e) {
            CANON_LOG_ERROR(CANON_LOG_ROOT())
                << "ConfigVar::toString exception" << e.what() << " convert: " << typeid(m_val).name() << " to string";
        }
        return "";
    }

    bool fromString(const std::string &val) override
    {
        try {
            setValue(FromStr()(val));
            return true;
        }
        catch (std::exception &e) {
            CANON_LOG_ERROR(CANON_LOG_ROOT())
                << "ConfigVar::toString exception" << e.what() << " convert: string to " << typeid(val).name();
        }
        return false;
    }

    T getValue() const { return m_val; }

    void setValue(const T &val)
    {
        m_val = val;
    }

    std::string getTypeName() override
    {
        return typeid(T).name();
    }

    uint64_t addListener(on_change_cb cb)
    {
        static uint64_t s_fun_id = 0;
        ++s_fun_id;
        m_cbs[s_fun_id] = cb;
        return s_fun_id;
    }

    on_change_cb getListener(uint64_t key)
    {
        auto it = m_cbs.find(key);
        return it == m_cbs.end() ? nullptr : it->second;
    }

    void clearListener()
    {
        m_cbs.clear();
    }

private:
    T m_val;
    std::map<uint64_t, on_change_cb> m_cbs;
};

class Config {
public:
    using ptr = std::shared_ptr<Config>;
    using ConfigVarMap = std::map<std::string, ConfigVarBase::ptr>;

    template <class T>
    static typename ConfigVar<T>::ptr Lookup(const std::string &name, const T &default_value,
                                             const std::string &description)
    {
        auto it = s_datas.find(name);
        if (it != s_datas.end()) {
            auto tmp = std::dynamic_pointer_cast<ConfigVar<T>>(it->second);
            if (tmp) {
                CANON_LOG_INFO(CANON_LOG_ROOT()) << "Lookup name = " << name << "exists";
                return tmp;
            } else {
                CANON_LOG_ERROR(CANON_LOG_ROOT()) << "Lookup name = " << name << "exists but type not "
                                                  << typeid(T).name() << " real_type: " << it->second->getTypeName()
                                                  << " " << it->second->toString();
                return nullptr;
            }
        }

        if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyz._12345678") != std::string::npos) {
            CANON_LOG_ERROR(CANON_LOG_ROOT()) << "Lookup name invalid " << name;
            throw std::invalid_argument(name);
        }

        typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
        s_datas[name] = v;
        return v;
    }

    template <class T>
    static typename ConfigVar<T>::ptr Lookup(const std::string &name)
    {
        auto it = s_datas.find(name);
        if (it == s_datas.end()) {
            return nullptr;
        }
        return std::dynamic_pointer_cast<ConfigVar<T>>(it->second);
    }

    static void LoadFromYaml(const YAML::Node &root);
    static ConfigVarBase::ptr LookupBase(const std::string &name);

private:
    static ConfigVarMap s_datas;
};

} // namespace canon

#endif // CANON_CONFIG_H
